My logo
Published on

Spring AOP源码解析

Spring AOP 源码解析

1. AOP 基础概念

1.1 什么是 AOP

Aspect Oriented Programming(面向切面编程)是一种编程范式,用于处理横切关注点(cross-cutting concerns)。在传统的面向对象编程(OOP)中,业务逻辑通常是自上而下执行的。以登录功能为例:

  1. 浏览器发起 HTTP 请求到 Controller
  2. Controller 接收请求,封装参数,进行验证
  3. 数据传递给 Service 层处理
  4. Service 调用 DAO 层
  5. DAO 操作数据库并返回结果

在这个过程中,会产生许多横切性问题,例如:

  • Controller 层的日志记录
  • Service 层的权限校验
  • DAO 层的事务处理

这些横切性问题与主业务逻辑无关,比如日志记录失败不会影响登录功能。如果不处理这些横切性问题,它们会散落在代码的各个角落。AOP 就是将这些横切性问题抽象出来,让程序员能够专注于处理这些横切关注点。

1.2 Spring AOP 简介

AOP 是一种编程目标,而实现这个目标的技术手段有多种:

  • JDK 动态代理
  • AspectJ 静态代理
  • Spring AOP

Spring AOP 是众多 AOP 实现方案中的一种,它基于动态代理技术实现。Spring AOP 的核心技术包括:

  • JDK 动态代理
  • CGLIB 动态代理
  • Spring 内部的 BeanPostProcessor

2. Spring AOP 核心概念

2.1 专业术语

Join Point(连接点)

  • 程序执行过程中的一个点
  • Spring AOP 中最小连接单位是方法
  • 一个连接点就是一个被连接的方法

Pointcut(切点)

  • 一组连接点的集合
  • 根据业务和功能对连接点进行分组
  • 相当于数据库中的表(连接点相当于表中的数据)

Advice(通知)

通知可以从三个维度理解:

  1. 通知内容:增强方法的具体业务逻辑(如日志记录、事务操作)
  2. 通知时机:
    • Before advice(前置通知)
    • After returning advice(返回通知)
    • After throwing advice(异常通知)
    • After (finally) advice(最终通知)
    • Around advice(环绕通知)
  3. 通知目标:通知需要作用到的连接点

Introduction(引入)

  • 在不修改代码的情况下让类实现某个接口
  • 可以指定接口的默认实现方法

Target Object(目标对象)

  • 被代理的对象
  • 在 Spring AOP 中,通过 CGLIB 或 JDK 动态代理实现增强

AOP Proxy(代理对象)

  • 被 JDK 动态代理或 CGLIB 动态代理后的对象

Weaving(织入)

  • 将切面和应用程序对象连接的过程

Aspect(切面)

  • 包含切点、连接点、通知等概念的类
  • 是 AOP 编程中的核心组件

3. Spring AOP 开发指南

3.1 启用 @AspectJ 支持

Java 配置方式

@Configuration
@EnableAspectJAutoProxy
@ComponentScan("jinggo.spring.example.aop")
public class App {
}

XML 配置方式

<aop:aspectj-autoproxy/>

3.2 声明切面

@Component 
@Aspect 
public class UserAspect { 
}

3.3 声明切点

@Component 
@Aspect 
public class UserAspect { 
    @Pointcut("execution(* com.yao.dao.UserDao.*(..))") 
    public void pointCut() {
        System.out.println("point cut"); 
    }
}

3.4 声明通知

@Component 
@Aspect 
public class UserAspect { 
    @Pointcut("execution(* com.yao.dao.UserDao.*(..))") 
    public void pointCut() {
        System.out.println("point cut"); 
    }

    @Before("com.yao.aop.UserAspect.pointCut()") 
    public void beforeAdvice() { 
        System.out.println("before"); 
    }
}

4. 切点表达式详解

4.1 execution 表达式

最常用的切点表达式,用于匹配方法执行:

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)

示例:

// 匹配 com.chenss.dao 包下任意接口和类的任意方法
@Pointcut("execution(* com.chenss.dao.*.*(..))")

// 匹配 com.chenss.dao 包下的任意接口和类的 public 方法
@Pointcut("execution(public * com.chenss.dao.*.*(..))")

// 匹配 com.chenss.dao 包下的任意接口和类的 public 无参数方法
@Pointcut("execution(public * com.chenss.dao.*.*())")

4.2 within 表达式

用于匹配特定类型中的连接点,粒度比 execution 更大:

// 匹配 com.chenss.dao 包中的任意方法
@Pointcut("within(com.chenss.dao.*)")

// 匹配 com.chenss.dao 包及其子包中的任意方法
@Pointcut("within(com.chenss.dao..*)")

4.3 args 表达式

用于匹配指定参数类型和数量的方法:

// 匹配第一个参数为 String 类型的方法
@Pointcut("execution(* com.chenss.dao.*.*(java.lang.String, ..))")

4.4 target 和 this 表达式

  • target:指向接口和子类
  • this:JDK 代理时指向接口和代理类,CGLIB 代理时指向接口和子类
// 目标对象为 IndexDaoImpl 类
@Pointcut("target(com.chenss.dao.IndexDaoImpl)")

// 当前代理对象
@Pointcut("this(com.chenss.dao.IndexDaoImpl)")

5. 最佳实践

  1. 优先使用 execution 表达式,它提供了最细粒度的控制
  2. 合理组织切面,将相关的横切关注点放在同一个切面中
  3. 使用有意义的切点名称,提高代码可读性
  4. 注意通知的执行顺序,避免复杂的依赖关系
  5. 合理使用环绕通知,它提供了最大的灵活性

Spring 事务详解

本文档主要介绍 Spring 框架中事务管理的两种主要方式:编程式事务和声明式事务,以及事务的传播机制。

编程式事务

通过 PlatformTransactionManager 或者 TransactionTemplate 可以实现编程式事务。

PlatformTransactionManager

关键代码

DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
definition.setName("name"); // 设置事务名称

TransactionStatus transaction = transactionManager.getTransaction(definition);
try {
    // 你的JDBC操作
    jdbcTemplate.update("update t set v=? where k=?", "n", "k2");
    // 提交
    transactionManager.commit(transaction);
} catch (Exception e) {
    e.printStackTrace();
    // 回滚
    transactionManager.rollback(transaction);
}

TransactionTemplate

关键代码

// 如果有返回值可以 new TransactionCallback
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
    @Override
    protected void doInTransactionWithoutResult(TransactionStatus status) {
        try {
            jdbcTemplate.update("update t set v=? where k=?", "n", "k2");
            // 不需要手动提交
        } catch (Exception e) {
            e.printStackTrace();
            // 但是需要手动回滚
            status.setRollbackOnly();
        }
    }
});

声明式事务

XML 配置声明式事务

XML 配置 (beans.xml)

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/aop
                           http://www.springframework.org/schema/aop/spring-aop.xsd
                           http://www.springframework.org/schema/tx
                           http://www.springframework.org/schema/tx/spring-tx.xsd
                           http://www.springframework.org/schema/context
                           http://www.springframework.org/schema/context/spring-context.xsd">

    <bean id="xmlUserService" class="com.test.transaction.service.XMLUserService">
        <property name="jdbcTemplate" ref="jdbcTemplate"></property>
    </bean>

    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="datasource"></property>
    </bean>

    <bean id="datasource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="username" value="root"></property>
        <property name="password" value="123456"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/shadow?useSSL=false&amp;allowPublicKeyRetrieval=true&amp;serverTimezone=UTC"></property>
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"></property>
    </bean>

    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="datasource"></property>
    </bean>

    <tx:advice transaction-manager="transactionManager" id="txAdvice">
        <tx:attributes>
            <tx:method name="update"/>
        </tx:attributes>
    </tx:advice>

    <aop:config>
        <aop:pointcut id="p" expression="execution(* com.test.transaction.service..*.*(..))"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="p"></aop:advisor>
    </aop:config>

</beans>

Java 代码 (XMLUserService.java)

public class XMLUserService {
    JdbcTemplate jdbcTemplate;

    // 通过xml配置注入jdbcTemplate 纯xml
    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    public void update() {
        jdbcTemplate.update("update t set v=? where k=?", "n", "k1");
        throw new NullPointerException();
    }
}

注解配置声明式事务

配置类 (App.java)

@Configuration
@EnableTransactionManagement
@ComponentScan("com.test.transaction")
public class App {

    @Bean
    public JdbcTemplate jdbcTemplate() {
        return new JdbcTemplate(dataSource());
    }

    @Bean
    public DataSource dataSource() {
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        dataSource.setPassword("123456");
        dataSource.setUrl("jdbc:mysql://localhost:3306/shadow?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC");
        dataSource.setUsername("root");
        return dataSource;
    }

    @Bean
    public PlatformTransactionManager transactionManager() {
        DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
        dataSourceTransactionManager.setDataSource(dataSource());
        return dataSourceTransactionManager;
    }
}

如果采用注解,需要在方法上面加 @Transactional

混合方式

这种方式没有固定标准,取决于个人喜好,可以选择哪些配置写入 XML,哪些使用 Java 代码。 需要说明的是,混合方式开启事务支撑可以在 XML 中配置,也可以在配置类中进行。

传播机制

Spring 中事务的传播行为定义在 Propagation 枚举中:

public enum Propagation {
    // 如果当前存在事务则加入该事务, 如果当前没有事务,则创建一个新的事务
    REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED),

    // 如果当前存在事务则加入该事务, 如果当前没有事务则以非事务的方式继续运行
    SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS),

    // 如果当前存在事务则加入该事务, 如果当前没有事务则抛出异常
    MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY),

    // 创建一个新的事务, 如果当前存在事务则把当前事务挂起
    REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW),

    // 以非事务方式运行, 如果当前存在事务则把当前事务挂起
    NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED),

    // 以非事务方式运行, 如果当前存在事务则抛出异常
    NEVER(TransactionDefinition.PROPAGATION_NEVER),

    // 如果当前存在事务则创建一个事务作为当前事务的嵌套事务来运行, 如果不存在则创建一个新的事务
    NESTED(TransactionDefinition.PROPAGATION_NESTED);
}

如何配置传播机制

编程式事务如何配置

TransactionTemplate

transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);

PlatformTransactionManager

DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
definition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);

声明式事务配置传播机制和回滚规则

注解方式:

@Transactional(noRollbackFor = IOException.class, propagation = Propagation.REQUIRED)

XML 方式:

<tx:method name="update" propagation="REQUIRED"/>

文中提及的 RuntimeException, ErrorExp, IOExp 为事务回滚规则中可能涉及的异常类型示例。